// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// NOTE: this file is translated from gpio_setup.py
//
// A script to create symlinks to platform specific GPIO pins.
//
// This script creates a set of symlinks pointing at the sys fs files returning
// the appropriate GPIO pin values. Each symlink is named to represent the
// actual GPIO pin function.
//
// The location of the symlinks generated by this script can be specified using
// the --symlink_root command line option. By default /home/gpio directory is
// used. The symlink directory must exist before this script is run.
//
// The GPIO pins' values are available through a GPIO device present in sys fs.
// The device is identified by its PCI bus address. The default PCI address of
// the GPIO device (set to 0:0:1f.0), can be changed using the --pci_address
// command line option.
//
// The platform specific bit usage of the GPIO device is derived from the ACPI,
// also using files found in a fixed location in sys fs. The default location of
// /sys/bus/platform/devices/chromeos_acpi could be changed using the
// --acpi_root command line option.
//
// Each GPIO pin is represented through ACPI as a subdirectory with several
// files in it. A typical name of the GPIO pin file looks as follows:
//
// <acpi_root>/GPIO.<instance>/GPIO.[0-3]
//
// where <instance> is a zero based number assigned to this GPIO pin by the BIOS
//
// In particular, file GPIO.0 represents encoded pin signal type (from which the
// symlink name is derived), and file GPIO.2 represents the actual zero based
// GPIO pin number within this GPIO device range.
//
// This script reads the ACPI provided mapping, enables the appropriate GPIO
// pins and creates symlinks mapping these GPIOs' values.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <ctype.h>
#include <getopt.h>
#include <glob.h>

#include <string>
#include <vector>
#include <map>

#define GPIO_ROOT               "/sys/class/gpio"
#define GPIO_DEVICE_ROOT        GPIO_ROOT "/gpiochip"
#define GPIO_ENABLE_FILE        GPIO_ROOT "/export"

// Can be changed using --pci_address command line option.
#define DEFAULT_GPIO_DEVICE_PCI_ADDRESS "0000:00:1f.0"

// Can be changed using --acpi_root command line option.
#define DEFAULT_ACPI_ROOT "/sys/bus/platform/devices/chromeos_acpi"

// can be changed using --symlink_root command line option.
#define DEFAULT_SYMLINK_ROOT "/home/gpio"

#define GPIO_SIGNAL_TYPE_EXTENSION      "0"
#define GPIO_ATTRIBUTES_EXTENSION       "1"
#define GPIO_PIN_NUMBER_EXTENSION       "2"

// Debug header signal type codes are offset by 0x100, the tuple below
// represents the range of valid codes for the debug header. The range
// starts at 0x100 and is 0x100 wide.
const int GPIO_DEBUG_HEADER_RANGE[2] = { 0x100, 0x100 };

// This dictionary maps GPIO signal types codes into their actual names.
const char *GPIO_SIGNAL_TYPES[] = {
  NULL,                         // note: 0 is NOT a valid index, so we must
                                // check the range by GPIO_SIGNAL_TYPE_MIN/MAX.
  "recovery_button",            // index: 1
  "developer_switch",           // index: 2
  "write_protect",              // index: 3
};

// calculate the dimension and min/max values.
#define GPIO_SIGNAL_TYPES_DIM \
  (sizeof(GPIO_SIGNAL_TYPES)/sizeof(GPIO_SIGNAL_TYPES[0]))
#define GPIO_SIGNAL_TYPE_MIN    (1)
#define GPIO_SIGNAL_TYPE_MAX    (GPIO_SIGNAL_TYPES_DIM-1)
// note: the above TYPE_MIN and TYPE_MAX refer to minimal and maximum valid
// index values, so the '1' in MAX definition (DIM-1) does not mean
// GPIO_SIGNAL_TYPE_MIN; it's for calculation to a 'zero-based array'.

///////////////////////////////////////////////////////////////////////
// Utilities for quick python-C translation

using std::string;
#define GPIO_STRING_BUFFER_LEN  (4096)  // general buffer length

// Works like throwing an exception - directly exit here.
static void GpioSetupError(const char *message, ...) {
  va_list args;
  va_start(args, message);
  vfprintf(stderr, message, args);
  fprintf(stderr, "\n");
  va_end(args);
  exit(1);
}

// Return: python - open(filename).read().strip()
static string open_read_strip(const char *filename) {
  FILE *fp = fopen(filename, "rt");
  string result;
  if (!fp)
    return "";

  // for virtual files (directly supported by kernel),
  // it is better to 'read one line of text and strip'.
  // for all files accessed by this utility should not
  // contain very long text in first line, so using
  // GPIO_STRING_BUFFER_LEN should be enough.
  char buffer[GPIO_STRING_BUFFER_LEN] = "";

  if (fgets(buffer, sizeof(buffer), fp) == NULL) {
    perror(filename);
  }
  fclose(fp);

  // now check leading and trailing spaces
  char *head = buffer, *tail = head + strlen(head);
  while (*head && isascii(*head) && isspace(*head))
    head++;
  while (tail > head && isascii(tail[-1]) && isspace(tail[-1]))
    tail--;

  return string(head, tail);
}

static string open_read_strip(const string &filename) {
  return open_read_strip(filename.c_str());
}

static bool os_path_exists(const char *filename) {
  return ::access(filename, 0) == 0;
}

static bool os_path_exists(const string &filename) {
  return os_path_exists(filename.c_str());
}

static void os_symlink(const string &src, const string &dest) {
  if (::symlink(src.c_str(), dest.c_str()) != 0) {
    perror(src.c_str());
    GpioSetupError("cannot create symlink (%s -> %s)",
                   src.c_str(), dest.c_str());
  }
}

static string os_readlink(const string &filename) {
  char buf[PATH_MAX] = "";
  if (::readlink(filename.c_str(), buf, sizeof(buf)-1) == -1) {
    perror(filename.c_str());
    GpioSetupError("cannot read link (%s)", filename.c_str());
  }
  return buf;
}

static string os_path_abspath(const string &src) {
  // TODO(hungte) there's no simple equivelent in POSIX.
  // since this is for debug message only, let's ignore it.
  return src;
}

static bool os_path_isdir(const char *filename) {
  struct stat st = {0};
  if (stat(filename, &st) != 0) {
    perror(filename);
    GpioSetupError("cannot query path status: %s", filename);
  }
  return S_ISDIR(st.st_mode);
}

static bool os_path_islink(const string &src) {
  struct stat st = {0};
  if (lstat(src.c_str(), &st) != 0) {
    perror(src.c_str());
    GpioSetupError("cannot query link status: %s", src.c_str());
  }
  return S_ISLNK(st.st_mode);
}

static string os_path_dirname(const string &filename) {
  // XXX here a full non-directory input (dir+file) name is assumed.
  assert(!os_path_isdir(filename.c_str()));
  string newname = filename;
  size_t pos_slash = newname.rfind('/');
  if (pos_slash != newname.npos)
    newname.erase(pos_slash);
  return newname;
}

typedef std::vector<string> GlobResult;

static GlobResult glob_glob(const string &pattern) {
  GlobResult r;
  glob_t globtok = {0};
  if (glob(pattern.c_str(),
           GLOB_ERR | GLOB_TILDE, NULL, &globtok) == 0) {
    for (size_t i = 0; i < globtok.gl_pathc; i++) {
      r.push_back(globtok.gl_pathv[i]);
    }
    globfree(&globtok);
  }
  return r;
}

// Return: python - format % (...)
static string format_string(const char *format, ...) {
  char buffer[GPIO_STRING_BUFFER_LEN];  // large enough for current version
  va_list args;
  va_start(args, format);
  vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);
  return buffer;
}

///////////////////////////////////////////////////////////////////////

// Represent GPIO chip available through sys fs.
// Attributes:
//  pci_address: a string, PCI address of this GPIO device
//  base: a number, base global GPIO number of this device (mapped to pin zero
//        in the device range)
//  capacity: a number, shows the number of GPIO pins of this device.
//  description: a multiline string description of this device, initialized
//               after the device is attached. Can be used to dump device
//               information.
class GpioChip {
 public:
  explicit GpioChip(const string &pci_address) {
    pci_address_ = pci_address;
    base_ = 0;
    capacity_ = 0;
    description_ = "not attached";
  }

  void Attach() {
    string f;
    GlobResult r = glob_glob(GPIO_DEVICE_ROOT "*/label");

    for (GlobResult::iterator i = r.begin(); i != r.end(); ++i) {
      string label = open_read_strip(*i);
      if (label == pci_address_) {
        f = *i;
        break;
      }
    }

    if (f.empty())
      GpioSetupError("could not find GPIO PCI device %s", pci_address_.c_str());

    string directory = os_path_dirname(f);
    base_ = atoi(open_read_strip(directory + "/base").c_str());
    capacity_ = atoi(open_read_strip(directory + "/ngpio").c_str());
    description_ = format_string(
        "GPIO device at PCI address %s\n"
        "Base gpio pin %d\n"
        "Capacity %d\n",
        pci_address_.c_str(), base_, capacity_);
  }

  // Enable a certain GPIO pin.
  // To enable the pin one needs to write its global GPIO number into
  // /sys/class/gpio/export, if this pin has not been enabled yet.
  // Inputs:
  //  pin: a number, zero based pin number within this device's range.
  void EnablePin(int pin) {
    if (pin >= capacity_)
      GpioSetupError("pin %d exceeds capacity of %d", pin, capacity_);

    // XXX in python version, this is named as 'global_gpio_number'
    // although it's not really global.
    int gpio_number = base_ + pin;

    string target = format_string("%s/gpio%d", GPIO_ROOT, gpio_number);
    if (!os_path_exists(target)) {
      FILE *fp = fopen(GPIO_ENABLE_FILE, "w");
      fprintf(fp, "%d", gpio_number);
      fclose(fp);
    }
  }

  // quick python-like translators
  int base() const { return base_; }
  int capacity() const { return capacity_; }

  operator const char *() const {
    return description_.c_str();
  }

 private:
  string pci_address_;
  int    base_;
  int    capacity_;
  string description_;

  GpioChip() { }
};

typedef struct {
  string name;
  int index;
  int pin_number;
} AcpiMappingEntry;

typedef std::vector<AcpiMappingEntry> AcpiMapping;

// Scan ACPI information about GPIO and generate a mapping.
//
// Returns: a list of tuples, each tuple consisting of a string representing
//          the GPIO pin name and a number, representing the GPIO pin within
//          the GPIO device space.
AcpiMapping ParseAcpiMappings(const string &acpi_root) {
  GlobResult r = glob_glob(format_string("%s/GPIO.[0-9]*", acpi_root.c_str()));
  AcpiMapping acpi_gpio_mapping;
  GlobResult::iterator i;

  for (i = r.begin(); i != r.end(); i++) {
    AcpiMappingEntry entry;
    const char *d = i->c_str();
    const char *dot_index_string = strrchr(d, '.');
    int signal_type = atoi(open_read_strip(
        format_string("%s/GPIO.%s", d, GPIO_SIGNAL_TYPE_EXTENSION)).c_str());

    assert(dot_index_string);
    entry.index = atoi(dot_index_string + 1);
    entry.pin_number = atoi(open_read_strip(
        format_string("%s/GPIO.%s", d, GPIO_PIN_NUMBER_EXTENSION)).c_str());

    if (signal_type >= GPIO_SIGNAL_TYPE_MIN &&
        signal_type <= static_cast<int>(GPIO_SIGNAL_TYPE_MAX) &&
        GPIO_SIGNAL_TYPES[signal_type] != NULL) {
      entry.name = GPIO_SIGNAL_TYPES[signal_type];
      acpi_gpio_mapping.push_back(entry);
      continue;
    }

    // This is not a specific signal, could be a debug header pin.
    int debug_header = signal_type - GPIO_DEBUG_HEADER_RANGE[0];
    if (debug_header >= 0 && debug_header < GPIO_DEBUG_HEADER_RANGE[1]) {
      entry.name = format_string("debug_header_%d", debug_header);
      acpi_gpio_mapping.push_back(AcpiMappingEntry(entry));
      continue;
    }

    // Unrecognized mapping, could happen if BIOS version is ahead of this
    // script.
    printf("unknown signal type encoding %d in %s\n", signal_type, d);
  }

  if (acpi_gpio_mapping.empty())
    GpioSetupError("no gpio mapping found. Is ACPI driver installed?");

  return acpi_gpio_mapping;
}

void CreateSymLink(const string &source_file, const string &symlink) {
    if (!os_path_exists(symlink)) {
      os_symlink(source_file.c_str(), symlink.c_str());
      return;
    }

    if (!os_path_islink(symlink))
      GpioSetupError("%s exists but is not a symlink",
                     os_path_abspath(symlink).c_str());
    if (os_readlink(symlink) != source_file)
      GpioSetupError("%s points to a wrong file",
                     os_path_abspath(symlink).c_str());
}

void CreateGpioSymlinks(const AcpiMapping& mappings,
                        GpioChip &gpio,
                        const char *acpi_root,
                        const char *symlink_root) {
  if (!os_path_exists(symlink_root))
    GpioSetupError("%s does not exist", symlink_root);

  if (!os_path_isdir(symlink_root))
    GpioSetupError("%s is not a directory", symlink_root);

  if (access(symlink_root, W_OK) != 0)
    GpioSetupError("%s is not writable", symlink_root);

  if (chdir(symlink_root) != 0)
    GpioSetupError("failed to change directory to %s", symlink_root);

  AcpiMapping::const_iterator i;
  for (i = mappings.begin(); i != mappings.end(); ++i) {
    string symlink, source_file;
    gpio.EnablePin(i->pin_number);

    symlink = i->name;
    source_file = format_string("%s/gpio%d/value", GPIO_ROOT,
                                i->pin_number + gpio.base());
    CreateSymLink(source_file, symlink);

    symlink = i->name + ".attr";
    source_file = format_string("%s/GPIO.%d/GPIO.%s",
                                acpi_root,
                                i->index,
                                GPIO_ATTRIBUTES_EXTENSION);
    CreateSymLink(source_file, symlink);
  }
}

static const char *__name__ = "";
static void usage_help_exit(int ret) {
  printf(
      "Usage: %s [options]\n"
      "\n"
      "Options:\n"
      "  -h, --help \t show this help message and exit\n"
      "  --symlink_root=SYMLINK_ROOT\n"
      "  --pci_address=PCI_ADDRESS\n"
      "  --acpi_root=ACPI_ROOT\n", __name__);
  exit(ret);
}

int main(int argc, char *argv[]) {
  __name__ = argv[0];

  struct GpioSetupOptions {
    string pci_address,
           symlink_root,
           acpi_root;
  } cmd_line_options;

  // copy default values
  cmd_line_options.pci_address = DEFAULT_GPIO_DEVICE_PCI_ADDRESS;
  cmd_line_options.symlink_root = DEFAULT_SYMLINK_ROOT;
  cmd_line_options.acpi_root = DEFAULT_ACPI_ROOT;

  // ProcessOptions();
  const struct option longopts[] = {
    { "pci_address", 1, NULL, 'p' },
    { "symlink_root", 1, NULL, 's' },
    { "acpi_root", 1, NULL, 'a' },
    { "help", 0, NULL, 'h'},
    { 0 },
  };

  int optc;
  while ((optc = getopt_long(argc, argv, "h", longopts, NULL)) != -1) {
    switch (optc) {
      case 'p':
        cmd_line_options.pci_address = optarg;
        break;

      case 's':
        cmd_line_options.symlink_root = optarg;
        break;

      case 'a':
        cmd_line_options.acpi_root = optarg;
        break;

      default:
        usage_help_exit(1);
        break;
    }
  }

  // currently no other non-dashed arguments allowed.
  if (optind != argc)
    usage_help_exit(1);

  GpioChip gpioc(cmd_line_options.pci_address);
  gpioc.Attach();
  CreateGpioSymlinks(
      ParseAcpiMappings(cmd_line_options.acpi_root),
      gpioc,
      cmd_line_options.acpi_root.c_str(),
      cmd_line_options.symlink_root.c_str());
  return 0;
}
